{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "DkA0Fobtb9dM"
   },
   "source": [
    "##### Copyright 2022 The Cirq Developers"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "cellView": "form",
    "id": "tUshu7YfcAAW"
   },
   "outputs": [],
   "source": [
    "# @title Licensed under the Apache License, Version 2.0 (the \"License\");\n",
    "# you may not use this file except in compliance with the License.\n",
    "# You may obtain a copy of the License at\n",
    "#\n",
    "# https://www.apache.org/licenses/LICENSE-2.0\n",
    "#\n",
    "# Unless required by applicable law or agreed to in writing, software\n",
    "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
    "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
    "# See the License for the specific language governing permissions and\n",
    "# limitations under the License."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "igOQCrBOcF5d"
   },
   "source": [
    "# Devices"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "LHRAvc9TcHOH"
   },
   "source": [
    "<table class=\"tfo-notebook-buttons\" align=\"left\">\n",
    "  <td>\n",
    "    <a target=\"_blank\" href=\"https://quantumai.google/cirq/hardware/devices\"><img src=\"https://quantumai.google/site-assets/images/buttons/quantumai_logo_1x.png\" />View on QuantumAI</a>\n",
    "  </td>\n",
    "  <td>\n",
    "    <a target=\"_blank\" href=\"https://colab.research.google.com/github/quantumlib/Cirq/blob/main/docs/hardware/devices.ipynb\"><img src=\"https://quantumai.google/site-assets/images/buttons/colab_logo_1x.png\" />Run in Google Colab</a>\n",
    "  </td>\n",
    "  <td>\n",
    "    <a target=\"_blank\" href=\"https://github.com/quantumlib/Cirq/blob/main/docs/hardware/devices.ipynb\"><img src=\"https://quantumai.google/site-assets/images/buttons/github_logo_1x.png\" />View source on GitHub</a>\n",
    "  </td>\n",
    "  <td>\n",
    "    <a href=\"https://storage.googleapis.com/tensorflow_docs/Cirq/docs/hardware/devices.ipynb\"><img src=\"https://quantumai.google/site-assets/images/buttons/download_icon_1x.png\" />Download notebook</a>\n",
    "  </td>\n",
    "</table>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "bd9529db1c0b"
   },
   "outputs": [],
   "source": [
    "try:\n",
    "    import cirq\n",
    "except ImportError:\n",
    "    print(\"installing cirq...\")\n",
    "    !pip install --quiet cirq\n",
    "    print(\"installed cirq.\")\n",
    "    import cirq"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "x6s7QnYKAdo7"
   },
   "source": [
    "## Validation basics\n",
    "\n",
    "When you are looking to run an algorithm on a real quantum computer (not a simulated one), there are often many additional constraints placed on the circuits you would like to run. Qubit connectivity, algorithm layout and the types of gates used in the circuit all become much more important. Cirq uses the abstract class `Device` to represent the constraints of an actual quantum processor. An example implementation of a device can be seen in the `cirq_google.Sycamore` class:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "BFcKF8Xp-A2S"
   },
   "outputs": [],
   "source": [
    "import cirq_google\n",
    "import networkx as nx\n",
    "\n",
    "my_device = cirq_google.Sycamore\n",
    "print(my_device)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "bN7O0XCLAH7G"
   },
   "source": [
    "This string representation of the device indicates the structure of the device and the connectivity of the qubits. In Sycamore's case, two-qubit gates can only be executed on qubits that are adjacent in the grid. Other constraints, like supported gates, are not shown in this representation.\n",
    "\n",
    "You can access all of the constraints indirectly by validating moments, operations and circuits with the `validate_***` method to verify if that structure would work on the device or not. In general, the `validate_***` method will tell you what part of your operation/moment/circuit does not fit the device's constraints, and why. All devices support this functionality. For the Sycamore device:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "53z2ucqZAFz7"
   },
   "outputs": [],
   "source": [
    "op1 = cirq.X(cirq.GridQubit(7, 7))\n",
    "\n",
    "try:\n",
    "    my_device.validate_operation(op1)\n",
    "except Exception as e:\n",
    "    print(e)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "jkgV8lhEB7L5"
   },
   "source": [
    "The previous example used a qubit that wasn't on the device, making the operation invalid. Most `validate_operation` implementations also take into account things like supported gates and connectivity as well:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "VM3kDY6QCRPY"
   },
   "outputs": [],
   "source": [
    "q1, q2, q3 = cirq.GridQubit(7, 4), cirq.GridQubit(7, 5), cirq.GridQubit(7, 6)\n",
    "op1 = cirq.H(q1)\n",
    "op2 = cirq_google.SYC(q1, q3)\n",
    "\n",
    "try:\n",
    "    my_device.validate_operation(op1)\n",
    "except Exception as e:\n",
    "    print(e)\n",
    "\n",
    "try:\n",
    "    my_device.validate_operation(op2)\n",
    "except Exception as e:\n",
    "    print(e)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "EcTgQg5xCxzG"
   },
   "source": [
    "These validation operations can also be used with moments of operations and full circuits:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "EiolyMKLC262"
   },
   "outputs": [],
   "source": [
    "op1 = cirq.X(q2)\n",
    "op2 = cirq_google.SYC(q1, q3)\n",
    "try:\n",
    "    my_device.validate_moment(cirq.Moment([op1, op2]))\n",
    "except Exception as e:\n",
    "    print(e)\n",
    "\n",
    "my_circuit = cirq.Circuit(\n",
    "    cirq.PhasedXPowGate(phase_exponent=0.3)(q1),\n",
    "    cirq.PhasedXPowGate(phase_exponent=0.3)(q2),\n",
    "    cirq_google.SYC(q1, q2),\n",
    "    cirq_google.SYC(q2, q3),\n",
    ")\n",
    "my_device.validate_circuit(my_circuit)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "-sNjRTCKDb2D"
   },
   "source": [
    "`op1` is allowed on qubit `q2`, but `op2` has the same invalid qubit target error as before. `validate_moment` finds this error by iterating the moment and stopping once the invalid operation is found. On the other hand, `my_circuit` satisfies all the device constraints and could be run on a Sycamore device, so `validate_circuit` does not throw an exception for it."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "BrxB0eCUDvlI"
   },
   "source": [
    "## Metadata features\n",
    "\n",
    "Some devices will also expose additional information via the `metadata` property. Metadata is usually exposed via the an instance (or subclass instance) of the `cirq.DeviceMetadata` class. You can access the metadata information of the Sycamore device with the `metadata` attribute:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "clsvdcXzEOIV"
   },
   "outputs": [],
   "source": [
    "metadata = my_device.metadata\n",
    "print(type(metadata))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "LvaYZjANb7_n"
   },
   "outputs": [],
   "source": [
    "issubclass(type(metadata), cirq.DeviceMetadata)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "dd2c56096511"
   },
   "source": [
    "The Sycamore device is a 2d grid device that exposes a `cirq.GridDeviceMetadata` with a uniform set of gates across all the qubits as well as a planar nearest neighbor connectivity graph. You can explore the properties below, starting with `qubit_set` and `nx_graph`, which are common to all instances and subclasses of the `cirq.DeviceMetadata` class.\n",
    "\n",
    "First, the set of qubits available are available in the `qubit_set` attribute."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "90ad647d9f10"
   },
   "outputs": [],
   "source": [
    "print(metadata.qubit_set)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "cb11944347e9"
   },
   "source": [
    "The `nx_graph` attribute details which of the `54` different qubits are connected to one another. Connected qubit pairs can execute two-qubit gates between them."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "3cd3a9c81874"
   },
   "outputs": [],
   "source": [
    "print(metadata.nx_graph)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "a3aaadba9455"
   },
   "source": [
    "`cirq.GridDeviceMetadata` has some attributes that are not automatically included in `cirq.DeviceMetadata`, including `gateset`, which indicates the types and families of Cirq gates that are accepted by all qubits across the device."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "JbwrPD_AETiX"
   },
   "outputs": [],
   "source": [
    "print(metadata.gateset)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "fo6ZTYN4HBVL"
   },
   "source": [
    "These metadata features can be useful when designing/building algorithms around certain device information in order to tailor them for that device."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "RvC4xz_OHT3s"
   },
   "source": [
    "## The `cirq.Device` interface\n",
    "\n",
    "For advanced users (such as vendors) it is also possible to implement your own Device with its own unique constraints and metadata information. Below is an example of a fictitious custom device:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "cA7AdVWJCpRr"
   },
   "outputs": [],
   "source": [
    "class MyDevice(cirq.Device):\n",
    "    \"\"\"Five qubits on a line, supporting X/Y/Z and CZ between neighbors.\"\"\"\n",
    "\n",
    "    def __init__(self):\n",
    "        # Specify the qubits available to the device\n",
    "        self._qubits = set(cirq.LineQubit.range(5))\n",
    "        # Specify which gates are valid\n",
    "        self._supported_gates = cirq.Gateset(\n",
    "            cirq.XPowGate, cirq.YPowGate, cirq.ZPowGate, cirq.CZPowGate\n",
    "        )\n",
    "\n",
    "    def validate_operation(self, operation):\n",
    "        \"\"\"Check to make sure `operation` is valid.\n",
    "\n",
    "        `operation` must be on qubits found on the device\n",
    "        and if it is a two qubit gate the qubits must be adjacent\n",
    "\n",
    "        Raises:\n",
    "          ValueError: if operation acts on qubits not found on the device.\n",
    "          ValueError: if two qubit gates have non-local interactions.\n",
    "          ValueError: if the operation is not in the supported gates.\n",
    "        \"\"\"\n",
    "        # Ensure that the operation's qubits are available on the device\n",
    "        if any(x not in self._qubits for x in operation.qubits):\n",
    "            raise ValueError(\"Using qubits not found on device.\")\n",
    "\n",
    "        # Ensure that the operation's qubits are adjacent if there are two of them\n",
    "        if len(operation.qubits) == 2:\n",
    "            p, q = operation.qubits\n",
    "            if not p.is_adjacent(q):\n",
    "                raise ValueError('Non-local interaction: {}'.format(repr(operation)))\n",
    "\n",
    "        # Ensure that the operation itself is a supported one\n",
    "        if operation not in self._supported_gates:\n",
    "            raise ValueError(\"Unsupported operation type.\")\n",
    "\n",
    "    def validate_circuit(self, circuit):\n",
    "        \"\"\"Check to make sure `circuit` is valid.\n",
    "\n",
    "        Calls validate_operation on all operations as well as imposing\n",
    "        a global limit on the total number of CZ gates.\n",
    "\n",
    "        Raises:\n",
    "          ValueError: if `validate_operation` raises for any operation in the\n",
    "            circuit.\n",
    "          ValueError: if there are more than 10 CZ gates in the entire circuit.\n",
    "        \"\"\"\n",
    "        # Call Device's `validate_operation`, which calls the `validate_operation`\n",
    "        #   function specified above on each operation in the circuit\n",
    "        super().validate_circuit(circuit)\n",
    "        # Ensure that no more than 10 two-qubit CZ gates exist in the circuit\n",
    "        cz_count = sum(1 for mom in circuit for op in mom if len(op.qubits) == 2)\n",
    "        if cz_count > 10:\n",
    "            raise ValueError(\"Too many total CZs\")\n",
    "\n",
    "    @property\n",
    "    def metadata(self):\n",
    "        \"\"\"MyDevice GridDeviceMetadata.\"\"\"\n",
    "        # Since `MyDevice` is planar it is a good idea to subclass the\n",
    "        # GridDeviceMetadata class to communicate additional device information to\n",
    "        # the user.\n",
    "        return cirq.GridDeviceMetadata(\n",
    "            qubit_pairs=[(p, q) for p in self._qubits for q in self._qubits if p.is_adjacent(q)],\n",
    "            gateset=self._supported_gates,\n",
    "        )"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "9oRUQuGGI7MJ"
   },
   "source": [
    "At absolute minimum, when creating a custom `Device`, you should inherit from `cirq.Device` and overwrite the `__init__` and `validate_operation` methods. \n",
    "\n",
    "This custom device can now be used to validate circuits:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "uFKsR1qLe24M"
   },
   "outputs": [],
   "source": [
    "my_custom_device = MyDevice()\n",
    "\n",
    "my_circuit = cirq.Circuit(\n",
    "    cirq.X(cirq.LineQubit(0)),\n",
    "    cirq.X(cirq.LineQubit(2)),\n",
    "    cirq.X(cirq.LineQubit(4)),\n",
    "    cirq.CZ(*cirq.LineQubit.range(2)),\n",
    ")\n",
    "too_many_czs = cirq.Circuit(cirq.CZ(*cirq.LineQubit.range(2)) for _ in range(11))\n",
    "\n",
    "# my_circuit is valid for my_custom_device.\n",
    "my_custom_device.validate_circuit(my_circuit)\n",
    "\n",
    "# each operation of too_many_czs is valid individually...\n",
    "for moment in too_many_czs:\n",
    "    for op in moment:\n",
    "        my_custom_device.validate_operation(op)\n",
    "\n",
    "# But the device has global constraints which the circuit does not meet:\n",
    "try:\n",
    "    my_custom_device.validate_circuit(too_many_czs)\n",
    "except Exception as e:\n",
    "    print(e)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "eG7JY2IVZGEQ"
   },
   "source": [
    "By default, the `validate_circuit` method of the `cirq.Device` class simply calls `validate_moment` on all the moments, which calls `validate_operation` on all the operations. It is advisable to maintain this behavior in your custom device, which can be implemented as above, by calling `super().validate_***` when writing each method.\n",
    "\n",
    "Depending on the scoping of constraints the custom device has, certain less local constraints might be better placed in `validate_moment` and certain global constraints might belong in `validate_circuit`. In addition to this, you can also add metadata options to your device. You can define a metadata subclass of `cirq.DeviceMetadata` or you can use an inbuilt metadata class like `cirq.GridDeviceMetadata`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "bbTat2DMf1VE"
   },
   "outputs": [],
   "source": [
    "my_metadata = my_custom_device.metadata\n",
    "\n",
    "# Display device graph:\n",
    "nx.draw(my_metadata.nx_graph)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "ddabb2b0bcc3"
   },
   "source": [
    "## Use in a virtual Engine\n",
    "\n",
    "`Device`s can also be used to specify `cirq.SimulatedLocalEngine` instances, which let you validate and simulate circuits using the same interface that the quantum hardware does. Read more in the [Virtual Engine Interface](../simulate/virtual_engine_interface.ipynb) page. \n",
    "\n",
    "Additionally, these virtual `Engine`s can be combined with noisy simulation that attempts to mimic existing hardware devices with the [Quantum Virtual Machine](../simulate/quantum_virtual_machine.ipynb). "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "OZxolGY3I82H"
   },
   "source": [
    "# Summary\n",
    "\n",
    "Devices in Cirq are used to specify constraints on circuits that are imposed by quantum hardware devices. You can check that an operation, moment, or circuit is valid on a particular `cirq.Device` by using `validate_operation`, `validate_moment`, or `validate_circuit` respectively. You can also create your own custom device objects to specify constraints for a new or changed device. Device objects, custom and otherwise, also can carry around metadata that may be useful for the validation process or other processes."
   ]
  }
 ],
 "metadata": {
  "colab": {
   "collapsed_sections": [],
   "name": "devices.ipynb",
   "toc_visible": true
  },
  "kernelspec": {
   "display_name": "Python 3",
   "name": "python3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}
